/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    package_manager.cpp  
  Description: 

*/

#include "stdafx.h"

using namespace m;
using namespace m::error;

#define TAG_PACKAGEMANAGER L"package_manager: "

namespace m
{
	m::package_manager::package_manager()
		: _Archiver(nullptr),
		_Serializer(nullptr),
		_Buffer(nullptr)
	{
	}

	m::package_manager::~package_manager()
	{
		clear(_Serializer);
		clear(_Archiver);
		clear(_Buffer);
	}


	void m::package_manager::save(const wchar_t* packageName, package* package, bool overwritable)
	{
		assert(packageName && package);
		if (packageName && package)
		{
			WCHAR TempDir[MAX_PATH] = { 0 };
			WCHAR TempName[MAX_PATH] = { 0 };
			if (GetTempPath(MAX_PATH, TempDir) > 0)
			{
				if (GetTempFileName(TempDir, L"pkg", 0, TempName) > 0)
				{
					DeleteFile(TempName);
				}
			}

			if (!*TempName)
			{
				throw bad_result_exception(MONORET(FACILITY_PACKAGE, ERROR_NOT_FOUND), "Failed to get path to archive.");
			}

			assign(package->_PackageName, (const wchar_t*)packageName);
			assign(package->_AssemblyDir, (const wchar_t*)TempName);
			package->_Overwrite = overwritable;
			this->send(L"us.sl.m.savePackageStart", package);
		}
	}

	void m::package_manager::process_entry()
	{
		DBGL(TAG_PACKAGEMANAGER L"Starting package manager.");

		while (started())
		{
			while (next_message());
		}
	}
	
	bool package_manager::handle_message(message * m)
	{
		ref_storage track1;
		process* sender = lock<process>(track1, m->sender());

		if (sender == this)
		{
			if (m->type_equals(L"us.sl.m.savePackageStart"))
			{
				if (m->data())
				{
					package* pkg = dynamic_cast<package*>(m->data());
					if (pkg)
					{
						WritePackage(pkg);
						this->send(L"us.sl.m.savePackageComplete", pkg);
					}
				}
				return true;
			}
		}
		return false;
	}

	void package_manager::WritePackage(package * package)
	{
		try
		{
			m::compression::_7za sz;

			if (!package->_Overwrite && sz.package_exists(package->_PackageName))
			{
				package->_Result = HRESULT_FROM_WIN32(ERROR_FILE_EXISTS);
				goto CLEANUP;
			}

			DWORD archiveAttrib = GetFileAttributes(package->_AssemblyDir);
			if (!(archiveAttrib != INVALID_FILE_ATTRIBUTES &&
				(archiveAttrib & FILE_ATTRIBUTE_DIRECTORY)))
			{
				if (FAILED(SHCreateDirectoryEx(NULL, package->_AssemblyDir, NULL)))
				{
					DBGW(GetLastError(), TAG_PACKAGEMANAGER L"SHCreateDirectoryEx('%S') failed", package->_AssemblyDir);
					package->_Result = LastHr;
					goto CLEANUP;
				}
			}

			//Add the files to the cabinet
			package::FileList* cur = package->_FirstFile;
			while (cur)
			{
				if (!WriteFile(package, cur))
				{
					DBGL(TAG_PACKAGEMANAGER L"Write failed.");
					goto CLEANUP;
				}
				cur = cur->Next;
			}

			if (package->metadata())
			{
				ref_storage track1, track2, track3;
				text::text_buffer* buffer = create(track1, new text::text_buffer());
				text::text_serializer* serializer = create(track2, new text::text_serializer());
				archiver* archive = create(track3, new archiver());
				assign(_Buffer, buffer);
				assign(_Serializer, serializer);
				assign(_Archiver, archive);

				_Archiver->archive(package->metadata(), _Serializer);
				package::FileList md;
				assign_new(md.Buffer, new io::file_buffer());
				_Serializer->save(_Buffer);
				_Buffer->save(md.Buffer, m::text::TextEncoding::TextEncodingUtf8);
				assign(md.Root, PACKAGE_METADATA);
				if (!WriteFile(package, &md))
				{
					DBGL(TAG_PACKAGEMANAGER L"Write failed.");
				}
			}

			sz.create_package(package->_PackageName, package->_AssemblyDir);

		CLEANUP:

			SHFILEOPSTRUCT file_op = {
				NULL,
				FO_DELETE,
				package->_AssemblyDir,
				L"",
				FOF_NOCONFIRMATION |
				FOF_NOERRORUI |
				FOF_SILENT,
				false,
				0,
				L"" };
			SHFileOperation(&file_op);

			if (SUCCEEDED(package->_Result))
			{
				DBGL(TAG_PACKAGEMANAGER L"Wrote package '%s'", package->_PackageName);
			}
			else
			{
				DBGL(TAG_PACKAGEMANAGER L"Write failed '%x'", package->_Result);
			}
		}
		catch (bad_result_exception& bre)
		{
			package->_Result = bre.ErrorCode;
		}
	}

	bool package_manager::WriteFile(package * package, package::FileList* fileData)
	{
		WCHAR* FileName;
		WCHAR CabinetPath[MAX_PATH] = { 0 };
		WCHAR CabinetDest[MAX_PATH] = { 0 };
		StringCchPrintf(CabinetDest, MAX_PATH, L"%s\\%s", package->_AssemblyDir, fileData->Root);

		if (GetFullPathName(CabinetDest, ARRAYSIZE(CabinetPath), CabinetPath, &FileName))
		{
			*FileName = NULL;
			DWORD archiveAttrib = GetFileAttributes(CabinetPath);
			if (!(archiveAttrib != INVALID_FILE_ATTRIBUTES &&
				(archiveAttrib & FILE_ATTRIBUTE_DIRECTORY)))
			{
				if (FAILED(SHCreateDirectoryEx(NULL, CabinetPath, NULL)))
				{
					DBGW(GetLastError(), TAG_PACKAGEMANAGER L"SHCreateDirectoryEx('%S') failed", CabinetPath);
					package->_Result = LastHr;
					return false;
				}
			}

			if (fileData->Buffer)
			{
				HANDLE Hnd = CreateFile(CabinetDest,
					GENERIC_WRITE,
					FILE_SHARE_READ,
					NULL,
					CREATE_NEW,
					FILE_ATTRIBUTE_NORMAL,
					NULL);
				if (Hnd == INVALID_HANDLE_VALUE)
				{
					DBGW(GetLastError(), TAG_PACKAGEMANAGER L"CreateFile('%S') failed", CabinetDest);
					package->_Result = LastHr;
					return false;
				}

				fileData->Buffer->set_offset(0);
				CHAR Buf[4096];
				DWORD bytesWritten = 0;
				do
				{
					bytesWritten = 0;

					size_t read = fileData->Buffer->read(Buf, ARRAYSIZE(Buf));
					if (read > 0)
					{
						if (!::WriteFile(Hnd, Buf, (DWORD)read, &bytesWritten, NULL))
						{
							DBGW(GetLastError(), TAG_PACKAGEMANAGER L"WriteFile('%S') failed", CabinetDest);
							package->_Result = LastHr;
							return false;
						}
					}
				} while (bytesWritten > 0);
				CloseHandle(Hnd);
			}
			else
			{
				if (!CopyFile(fileData->FilePath, CabinetDest, TRUE))
				{
					DBGW(GetLastError(), TAG_PACKAGEMANAGER L"CopyFile('%S') failed", CabinetDest);
					package->_Result = LastHr;
					return false;
				}
			}

			return true;
		}
		if (SUCCEEDED(package->_Result))
		{
			package->_Result = HRESULT_FROM_WIN32(ERROR_INVALID_OPERATION);
		}
		return false;
	}
}